using System;
using System.Linq;
using Cooking.Core.DataInterfaces;
using Cooking.Core.Dto;
using Cooking.Core.Infrastructure;
using Cooking.Core.Model;

namespace Cooking.Core.Services
{
    public class RecipeService : IRecipeService
    {
        private const string CreateRecipeMessage = "Created recipe: {0}";
        private const string CreateRecipeDuplicateMessage = "{0} already exists -- created as {1}";

        private readonly IRecipeDao _recipeDao;
        private IIngredientService _ingredientService;

        public RecipeService(IRecipeDao dao)
        {
            _recipeDao = dao;
        }

        public RecipeService(IRecipeDao recipeDao, IIngredientService ingredientService)
        {
            _recipeDao = recipeDao;
            _ingredientService = ingredientService;
        }


        /// <summary>
        /// Creates a new recipe and saves to the data layer.
        /// </summary>
        public RecipeCreateResult CreateNewRecipe(string title)
        {
            var existing = _recipeDao.FindByName(title);

            string recipeName = existing == null ? title : RenameDuplicateRecipe(title);
            string message = existing == null ? CreateRecipeMessage.ToFormat(title) : CreateRecipeDuplicateMessage.ToFormat(title, recipeName);
            int id = _recipeDao.Create(new Recipe {Name = recipeName});

            return new RecipeCreateResult {Created = true, Message = message, RecipeId = id};
        }

        /// <summary>
        /// If a recipe already exists with the given name, the data is appened to make it unique.
        /// </summary>
        private string RenameDuplicateRecipe(string title)
        {
            return title + DateTime.Now.ToString(" MMMM d, yyyy @ HH:mm");
        }

        private void AddIngredientToRecipe(int recipeId, string ingredient, string unitOfMeasure, decimal quantity, string ingredientPrep)
        {
            Ingredient toAdd = _ingredientService.LoadIngredientOrCreateIfMissing(ingredient);
            Recipe owner = _recipeDao.LoadById(recipeId);

            RecipeIngredient recipeIngredient = new RecipeIngredient { Ingredient = toAdd, IngredientPrep = ingredientPrep, Quantity = quantity, UnitOfMeasure = unitOfMeasure};
            owner.AddIngredient(recipeIngredient);
            _recipeDao.Save(owner);
        }

        /// <summary>
        /// Adds an ingredient to a recipe.
        /// </summary>
        public void AddIngredientToRecipe(AddToRecipeDto dto)
        {
            AddIngredientToRecipe(dto.RecipeId, dto.IngredientName, dto.UnitOfMeasure, dto.Quantity, dto.IngredientPrep);
        }

        /// <summary>
        /// Loads a recipe
        /// </summary>
        public Recipe LoadRecipe(int recipeId)
        {
            Recipe recipe = _recipeDao.LoadById(recipeId);

            if (recipe == null)
            {
                throw new ArgumentException("No recipe found");
            }
            return recipe;
        }

        public void ChangeIngredient(UpdateRecipeIngredientDto updateDto)
        {
            var recipe = _recipeDao.LoadById(updateDto.RecipeId);
            var toUpdate = recipe.Ingredients.Single(i => i.Id == updateDto.RecipeIngredientId);

            toUpdate.Quantity = updateDto.Quantity;
            toUpdate.UnitOfMeasure = updateDto.UnitOfMeasure;

            _recipeDao.Save(recipe);
        }

        public void RemoveIngredient(int recipeId, int recipeIngredientId)
        {
            var recipe = _recipeDao.LoadById(recipeId);
            recipe.RemoveIngredient(recipe.Ingredients.First(x => x.Id == recipeIngredientId));
            _recipeDao.Save(recipe);
        }

        public void AddStepToRecipe(int recipeId, string description)
        {
            var recipe = _recipeDao.LoadById(recipeId);
            recipe.AddStep(new PreparationStep { Description = description, SequenceNumber = recipe.Steps.Count + 1} );
            _recipeDao.Save(recipe);
        }

        public void UpdateStep(int recipeId, int stepId, string description)
        {
            var recipe = _recipeDao.LoadById(recipeId);
            recipe.Steps.First(x => x.Id == stepId).Description = description;
            _recipeDao.Save(recipe);
        }

        public void RemoveStep(int recipeId, int stepId)
        {
            var recipe = _recipeDao.LoadById(recipeId);
            recipe.Steps.Remove(recipe.Steps.First(step => step.Id == stepId));
            _recipeDao.Save(recipe);
        }

        public void MoveStepUp(int recipeId, int stepId)
        {
            var recipe = _recipeDao.LoadById(recipeId);
            Reorderer<PreparationStep> reorder = new Reorderer<PreparationStep>(recipe.Steps);
            reorder.MoveUp(recipe.Steps.First(x => x.Id == stepId));
            _recipeDao.Save(recipe);
        }

        public void MoveStepDown(int recipeId, int stepId)
        {
            var recipe = _recipeDao.LoadById(recipeId);
            Reorderer<PreparationStep> reorder = new Reorderer<PreparationStep>(recipe.Steps);
            reorder.MoveDown(recipe.Steps.First(x => x.Id == stepId));
            _recipeDao.Save(recipe);
        }
    }
}